package com.summer.data;

import com.summer.data.connection.PageInfoHolder;
import com.summer.data.dialect.Dialect;
import com.summer.data.jql.Context;
import com.summer.data.jql.JsqlAble;
import com.summer.data.jql.behavior.*;
import com.summer.data.jql.builder.*;
import com.summer.data.jql.compose.FieldPack;
import com.summer.data.jql.compose.OrPack;
import com.summer.data.jql.concept.Field;
import com.summer.data.jql.concept.NativeValue;
import com.summer.data.jql.concept.other.Star;
import com.summer.data.jql.with.*;
import com.summer.data.rs.datatype.PageInfo;
import com.summer.data.table.BaseTable;
import com.summer.data.jql.table.JqlTable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author 345480567@qq.com
 */

public class Jql extends Context implements JsqlAble, QueryAble {

    SelectBuilder selectBuilder = new SelectBuilder(this);

    FromBuilder fromBuilder = new FromBuilder(this);

    WhereBuilder whereBuilder = new WhereBuilder(this);

    enum AND_OR_POS {
        INIT, WHERE, HAVING
    }

    private AND_OR_POS pos = AND_OR_POS.INIT;

    // boolean startWhere = false;
    GroupBuilder groupBuilder = new GroupBuilder(this);

    HavingBuilder havingBuilder = new HavingBuilder(this);

    OrderBuilder orderBuilder = new OrderBuilder(this);

    private PageInfo pageInfo = PageInfo.get();

    public static Jql create() {
        return new Jql();
    }

    public Jql() {
        super(true);
    }

    public Jql alias(TableAble tableAble, String aliasName) {
        aliasMap.put(tableAble, aliasName);
        return this;
    }

    public Jql distinct() {
        selectBuilder.distinct();
        return this;
    }

    public Jql select(SelectAble... exps) {
        selectBuilder.add(exps);
        return this;
    }

    public Jql select(Object... exps) {
        for (Object exp : exps) {
            if (exp instanceof SelectAble) {
                selectBuilder.add((SelectAble)exp);
            } else if (exp instanceof BaseTable) {
                BaseTable baseTable = (BaseTable)exp;
                selectBuilder.add(new Star(baseTable));
            }
        }
        return this;
    }

    public Jql select(BaseTable table) {
        selectBuilder.add(new Star(table));
        return this;
    }

    // public Query select(int val) {
    // return this;
    // }

    // public Query select(String val) {
    // return this;
    // }

    public Jql from(TableAble queryAble) {
        fromBuilder.from(queryAble);
        return this;
    }

    public Jql from(Object queryAble) {
        // todo: ydfqy
//        fromBuilder.from(queryAble);
        return this;
    }

    public Jql join(TableAble queryAble) {
        fromBuilder.join(queryAble);
        return this;
    }

    public Jql leftJoin(TableAble queryAble) {
        fromBuilder.leftJoin(queryAble);
        return this;
    }

    public Jql on(FilterAble... exps) {
        fromBuilder.on(exps);
        return this;
    }

    public Jql where() {
        // startWhere = true;
        pos = AND_OR_POS.WHERE;
        return this;
    }

    public Jql where(FilterAble... exps) {
        // startWhere = true;
        pos = AND_OR_POS.WHERE;
        for (FilterAble exp : exps) {
            and(exp);
        }
        return this;
    }

    public Jql having() {
        // startWhere = true;
        pos = AND_OR_POS.HAVING;
        return this;
    }

    public Jql having(FilterAble... exps) {
        // startWhere = true;
        pos = AND_OR_POS.HAVING;
        for (FilterAble exp : exps) {
            and(exp);
        }
        return this;
    }

    public Jql and(FilterAble exp) {
        switch (pos) {
            case INIT:
                fromBuilder.and(exp);
                break;
            case WHERE:
                whereBuilder.and(exp);
                break;
            case HAVING:
                havingBuilder.and(exp);
            default:
                System.err.println("无法识别 and 添加的位置。");
        }
        return this;
    }

    public Jql or(FilterAble exp) {
        switch (pos) {
            case INIT:
                fromBuilder.or(exp);
                break;
            case WHERE:
                whereBuilder.or(exp);
                break;
            case HAVING:
                havingBuilder.or(exp);
            default:
                System.err.println("无法识别 or 添加的位置。");
        }
        return this;
    }

    public Jql group(GroupAble... exps) {
        groupBuilder.group(exps);
        return this;
    }

    public Jql order(OrderAble... exps) {
        orderBuilder.order(exps);
        return this;
    }

    // 并集
    public Jql union(QueryAble another) {
        JqlLinker jqlLinker = new JqlLinker();
        jqlLinker.add(this);
        jqlLinker.union(another);
        return jqlLinker;
    }

    // 并集
    public Jql unionAll(QueryAble another) {
        JqlLinker jqlLinker = new JqlLinker();
        jqlLinker.add(this);
        jqlLinker.union(another);
        return jqlLinker;
    }

    // 交集
    public Jql interSect(QueryAble another) {
        JqlLinker jqlLinker = new JqlLinker();
        jqlLinker.add(this);
        jqlLinker.union(another);
        return jqlLinker;
    }

    // 差集，返回两个结果集中不同的部分
    // oracle 支持
    public Jql minus(QueryAble another) {
        JqlLinker jqlLinker = new JqlLinker();
        jqlLinker.add(this);
        jqlLinker.minus(another);
        return jqlLinker;
    }

    // 从左结果集中去掉在右结果集中出现的后返回
    // mysql 不支持
    public Jql except(QueryAble another) {
        JqlLinker jqlLinker = new JqlLinker();
        jqlLinker.add(this);
        jqlLinker.except(another);
        return jqlLinker;
    }

    public Jql setPage(Integer pageNo, Integer pageSize) {
        assert pageNo > 0;
        assert pageSize > 0;
        pageInfo = new PageInfo(pageNo, pageSize);
        return this;
    }

    public PageInfo getPageInfo() {
        return pageInfo;
    }

    // public BoolExpression andPack(BoolExpression... exps) {
    // return new AndPack(exps);
    // }

    public <T> NativeValue<T> nativeVal(T value) {
        return new NativeValue<>(value);
    }

    public FilterAble pack(FilterAble exp1, FilterAble... exps) {
        List<FilterAble> expList = new ArrayList<>();
        expList.add(exp1);
        Collections.addAll(expList, exps);
        return new OrPack(expList.toArray(new FilterAble[0]));
    }

    public FieldPack pack(Field<?> exp1, Field<?>... exps) {
        List<Field<?>> expList = new ArrayList<>();
        expList.add(exp1);
        Collections.addAll(expList, exps);
        return new FieldPack(expList);
    }

    @Override
    public Jsql buildJsql(Dialect dialect) {
        fromBuilder.init();
        Jsql jsql = new Jsql();
        jsql.append(selectBuilder.buildJsql(dialect));
        jsql.append(fromBuilder.buildJsql(dialect));
        jsql.append(whereBuilder.buildJsql(dialect));
        jsql.append(groupBuilder.buildJsql(dialect));
        jsql.append(orderBuilder.buildJsql(dialect));
        return jsql;
    }

    @Override
    public Jsql buildPageJsql(Dialect dialect, PageInfo pageInfo) {
        fromBuilder.init();
        Jsql jsql = new Jsql();
        jsql.append(selectBuilder.buildPageJsql(dialect, pageInfo));
        jsql.append(fromBuilder.buildPageJsql(dialect, pageInfo));
        jsql.append(whereBuilder.buildPageJsql(dialect, pageInfo));
        jsql.append(groupBuilder.buildPageJsql(dialect, pageInfo));
        jsql.append(orderBuilder.buildPageJsql(dialect, pageInfo));

        return dialect.getPageSql(jsql, pageInfo);
    }

    public Jsql buildPageJsql(Dialect dialect) {

        if (pageInfo == null) {
            pageInfo = PageInfoHolder.getPageInfo();
            if (pageInfo == null) {
                pageInfo = new PageInfo(1, 10);
            }
        }

        return buildPageJsql(dialect, pageInfo);
    }

    public JqlTable asTable() {
        //        alias(jqlTableAble, aliasName);
        return new JqlTable(this);
    }

    public static <T1 extends TableAble> With1<T1> with(Class<T1> clazz1) {
        return new With1<>(clazz1);
    }

    public static <T1 extends TableAble, T2 extends TableAble> With2<T1, T2> with(Class<T1> clazz1, Class<T2> clazz2) {
        return new With2<>(clazz1, clazz2);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble> With3<T1, T2, T3> with(
            Class<T1> clazz1, Class<T2> clazz2, Class<T3> clazz3) {
        return new With3<>(clazz1, clazz2, clazz3);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble, T4 extends TableAble> With4<T1, T2, T3, T4> with(
            Class<T1> clazz1, Class<T2> clazz2, Class<T3> clazz3, Class<T4> clazz4) {
        return new With4<>(clazz1, clazz2, clazz3, clazz4);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble, T4 extends TableAble, T5 extends TableAble> With5<T1, T2, T3, T4, T5> with(
            Class<T1> clazz1, Class<T2> clazz2, Class<T3> clazz3,
            Class<T4> clazz4, Class<T5> clazz5) {
        return new With5<>(clazz1, clazz2, clazz3, clazz4, clazz5);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble, T4 extends TableAble, T5 extends TableAble, T6 extends TableAble> With6<T1, T2, T3, T4, T5, T6> with(
            Class<T1> clazz1, Class<T2> clazz2,
            Class<T3> clazz3, Class<T4> clazz4,
            Class<T5> clazz5, Class<T6> clazz6) {
        return new With6<>(clazz1, clazz2, clazz3, clazz4, clazz5, clazz6);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble, T4 extends TableAble, T5 extends TableAble, T6 extends TableAble, T7 extends TableAble> With7<T1, T2, T3, T4, T5, T6, T7> with(
            Class<T1> clazz1, Class<T2> clazz2,
            Class<T3> clazz3, Class<T4> clazz4,
            Class<T5> clazz5, Class<T6> clazz6,
            Class<T7> clazz7) {
        return new With7<>(clazz1, clazz2, clazz3, clazz4, clazz5, clazz6, clazz7);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble, T4 extends TableAble, T5 extends TableAble, T6 extends TableAble, T7 extends TableAble, T8 extends TableAble> With8<T1, T2, T3, T4, T5, T6, T7, T8> with(
            Class<T1> clazz1, Class<T2> clazz2,
            Class<T3> clazz3, Class<T4> clazz4,
            Class<T5> clazz5, Class<T6> clazz6,
            Class<T7> clazz7, Class<T8> clazz8) {
        return new With8<>(clazz1, clazz2, clazz3, clazz4, clazz5, clazz6, clazz7, clazz8);
    }

    public static <T1 extends TableAble, T2 extends TableAble, T3 extends TableAble, T4 extends TableAble, T5 extends TableAble, T6 extends TableAble, T7 extends TableAble, T8 extends TableAble, T9 extends TableAble> With9<T1, T2, T3, T4, T5, T6, T7, T8, T9> with(
            Class<T1> clazz1, Class<T2> clazz2,
            Class<T3> clazz3, Class<T4> clazz4,
            Class<T5> clazz5, Class<T6> clazz6,
            Class<T7> clazz7, Class<T8> clazz8,
            Class<T9> clazz9) {
        return new With9<>(clazz1, clazz2, clazz3, clazz4, clazz5, clazz6, clazz7, clazz8, clazz9);
    }

}
